'''
Created on 06.03.2012

@author: myaut
'''

import os
import sys
import getopt

import salsa
from salsa import do_trace_interrupt

from salsa.util.io import IOTransactionError

from salsa.coresvc import CoreClient, SALSACoreException
from salsa.service import Client

from salsa.service.stree import SourceState

motd = 'SALSA SP 0.1 by Sergey Klyaus'

class CLIUsageError(Exception):
    pass

class CLIService:
    def __init__(self):
        self.client = Client(self.svc_name, self.svc_class)
    
    def stop(self):
        self.client.stop()

class CLISubsystem:        
    @classmethod
    def add_commands(cls): 
        cls.subcommands = {}
        
        for attr_name in dir(cls):
            attr = getattr(cls, attr_name)
            
            # print cls.__name__, attr, 'cmd_name' in dir(attr)
            
            if 'cmd_name' in dir(attr):
                cls.subcommands[attr.cmd_name] = attr
    
    @classmethod
    def print_usage(cls):
        for (cmd_name, cmd) in cls.subcommands.items():
            print >> sys.stderr, "  %s\t- %s" % (cmd_name, cmd.cmd_info)
    
    @classmethod
    def print_cmd_usage(cls, cmd_name):
        cmd = cls.subcommands[cmd_name]
        
        print >> sys.stderr, cmd_name
        print >> sys.stderr, '\t', cmd.cmd_info 
        print >> sys.stderr, '\t'
        print >> sys.stderr, "\tUsage: %s" % (cmd.cmd_usage)
        
        if 'cmd_ext_info' in dir(cmd):
            cmd_ext_info = getattr(cmd, 'cmd_ext_info')
            print >> sys.stderr, cmd_ext_info
            
    def exec_cmd(self, cmd_name, args):
        if cmd_name == '-?':
            self.print_usage()
            sys.exit(0) 
        
        if '-?' in args:
            self.print_cmd_usage(cmd_name)
            sys.exit(0) 
        
        if cmd_name not in self.subcommands:
            print >> sys.stderr, 'Unknown command %s' % cmd_name
            print >> sys.stderr, 'Use salsacli -? to print usage'
            
            sys.exit(1)
        
        cmd = self.subcommands[cmd_name]
        cmd(self, args)

class CLICore(CLISubsystem):
    name = "core"
    
    def stop(self):
        pass
    
    def list_tasks(self, args):
        if '-s' in args:
            for svc in salsa.core.list_services():
                print svc
        else:
            fmt_str = '%16s %8s %s' 
            print fmt_str % ('TASKID', 'PID', 'NAME')
            
            for taskid in salsa.core.list_tasks():
                info = salsa.core.get_task_info(taskid)
                
                name = info['name']
                pid = info['pid']
                
                print fmt_str % (taskid, pid, name)
                
                if '-v' not in args:
                    continue
                
                if info['type'] == 'service':
                    print '\tHost:', info['host']
                    print '\tPort:', info['port']
                
                for argname, argvalue in info['args'].items():
                    print '\tArgument: %s = %s' % (argname, argvalue)
                
                for paramname, paramvalue in info['params'].items():
                    print '\tParameter: %s = %s' % (paramname, paramvalue)
    
    list_tasks.cmd_name = "tasks"
    list_tasks.cmd_usage = "tasks [-s] [-v]"
    list_tasks.cmd_info = "List of currently executing tasks. -s to show only services. -v to be verbose"
    
    def process(self, args):
        salsa.core.create_task('process')
    
    process.cmd_name = 'process'
    process.cmd_usage = 'process'
    process.cmd_info = 'Runs processing for entire tree'
    
    def parse(self, args):
        if len(args) != 1:
            raise CLIUsageError('Missing argument')
        
        salsa.core.create_task('parse', {'src_fn': args[0]})
    
    parse.cmd_name = 'parse'
    parse.cmd_usage = 'parse src_name'
    parse.cmd_info = 'Parses source'
    
    def preproc(self, args):
        if len(args) != 1:
            raise CLIUsageError('Missing argument')
        
        salsa.core.create_task('preproc', {'src_fn': args[0]})
    
    preproc.cmd_name = 'preproc'
    preproc.cmd_usage = 'preproc src_name'
    preproc.cmd_info = 'Preprocess source'
    
class CLIRepository(CLISubsystem, CLIService):
    name = "repo"
    svc_name = "svc.repo"
    
    from salsa.service.repo import Repository as svc_class
    
    def cat_files(self, args):
        for hash in args:
            path = self.client.abs_repo_path(hash)
            
            with open(path, 'r') as f:
                sys.stdout.write(f.read())
    
    cat_files.cmd_name = 'cat'
    cat_files.cmd_usage = 'cat hash1 [hash2] ...'
    cat_files.cmd_info = 'Dumps content of file from permanent repository to stdout'
    
    def list_repo(self, args):
        if len(args) > 1:
            raise CLIUsageError('Too much arguments')
        
        walker = self.client.list_repo(*args)
        
        fmt = '%40s %12s %s' 
        print fmt % ('HASH', 'NAME', 'TAG')
        
        for f in walker:
            hash = f[0]
            name = f[1]['name']
            tag = f[1]['tag']
            
            print fmt % (hash, name, tag)
    
    list_repo.cmd_name = 'list'
    list_repo.cmd_usage = 'list [storage]'
    list_repo.cmd_info = 'List all files in repository'
    
    
    def destroy(self, args):
        if len(args) == 0:
            print >> sys.stderr, 'Must be at least one argument'
            sys.exit(1)
        
        if len(args) > 1:
            self.client.destroy_file(args[0], args[1])
        else:
            self.client.destroy_file(args[0])        
    
    destroy.cmd_name = 'destroy'
    destroy.cmd_usage = 'destroy hash'
    destroy.cmd_info = 'Removes file from permanent repository'

class CLISourceTree(CLISubsystem, CLIService):
    name = "stree"
    svc_name = "svc.stree"
    
    from salsa.service.stree import SourceTree as svc_class
    
    def init_tree(self, args):
        self.client.init_tree()
        
    init_tree.cmd_name = 'init'
    init_tree.cmd_usage = 'init'
    init_tree.cmd_info = 'Initializes tree'
    
    def print_info(self, src_name):
        info = self.client.source_info(src_name)
        
        print 'Source: %s' % src_name
        print 'State: %s' % SourceState(info['state'])
        
        if info['state'] == SourceState.S_FAILED:
            print 'Message: %s' % info['failmsg']
        
        print '\nFiles: '
        
        for f in info['files']:
            print '\t%s (%s)' % (f[1], SourceState(f[0]))
        
        print '\nDependencies: '
        
        for d in info['deps']:
            print '\t%s (%s)' % (d[0], SourceState(d[1]))
        
    
    def info(self, args):
        if len(args) == 0:
            print >> sys.stderr, 'Must be at least one argument'
            sys.exit(1)
        
        for src_name in args:
            self.print_info(src_name)
    
    info.cmd_name = 'info'
    info.cmd_usage = 'info src_name [src_name...]'
    info.cmd_info = 'Shows information about source'
        
    def rollback(self, args):
        if len(args) == 0:
            print >> sys.stderr, 'Must be at least one argument'
            sys.exit(1)
        
        for src_name in args:
            self.client.rollback_source(src_name)
    
    rollback.cmd_name = 'rollback'
    rollback.cmd_usage = 'rollback [-f] [src_name]'
    rollback.cmd_info = 'Rollback source to ADDED state. Removes file refs (NOT files) and deps. '
    
    def clear(self, args):       
        if len(args) != 0:
            print >> sys.stderr, 'Extra arguments found'
            sys.exit(1)
        
        walker = self.client.walk_tree()
        failed_sources = {}
        
        for (id, name, status, failmsg) in walker:
            if status == SourceState.S_FAILED:
                failed_sources[name] = failmsg
        
        for src_fn in failed_sources:
            self.client.rollback_source(src_fn)
            
            print 'Rolled back source %s' % src_fn
            print '\tFault was: %s' % failed_sources[src_fn]
    
    clear.cmd_name = 'clear'
    clear.cmd_usage = 'clear'
    clear.cmd_info = 'Rolls back all failed sources'
    
    def delete(self, args):
        if len(args) == 0:
            print >> sys.stderr, 'Must be at least one argument'
            sys.exit(1)
        
        for src_name in args:
            self.client.delete_source(src_name)
    
    delete.cmd_name = 'delete'
    delete.cmd_usage = 'delete [src_name]'
    delete.cmd_info = 'Rollback source and delete it from tree '
    
    def list_tree(self, args):
        walker = self.client.walk_tree()
        
        fmt = '%6s %8s   %s' 
        print fmt % ('ID', 'STATUS', 'NAME')
        
        only_failed = '-f' in args
        
        for f in walker:
            id = f[0]
            name = f[1]
            status = SourceState(f[2])
            failmsg = f[3]
            
            if only_failed and status.ss != SourceState.S_FAILED:
                continue
            
            print fmt % (id, status, name)
            
            if status.ss == SourceState.S_FAILED:
                print '\tFAIL: ' + failmsg.strip()
    
    list_tree.cmd_name = 'list'
    list_tree.cmd_usage = 'list [-f]'
    list_tree.cmd_info = 'Shows all sources in tree (-f to show only failed sources)'
    
    def stat(self, args):
        walker = self.client.walk_tree()
        
        stats = {SourceState.S_ADDED: 0,
                 SourceState.S_FAILED: 0,
                 SourceState.S_PREPROC: 0,
                 SourceState.S_PARSED: 0 }
        
        for f in walker:
            stats[f[2]] += 1
            
        for (state, count) in stats.items():
            print str(SourceState(state)) + ':', count 
    
    stat.cmd_name = 'stat'
    stat.cmd_usage = 'stat'
    stat.cmd_info = 'Shows count of sources according to their states'
    
    def add(self, args):
        if len(args) == 0:
            print >> sys.stderr, 'Must be at least one argument'
            sys.exit(1)
        
        for src_name in args:
            self.client.add_source_ext(src_name)
    
    add.cmd_name = 'add'
    add.cmd_usage = 'add src_name [src_name...]'
    add.cmd_info = 'Adds source into source tree'

class CLITypeDB(CLISubsystem, CLIService):
    name = "typedb"
    svc_name = "svc.typedb"
    
    from salsa.service.typedb import TypeDB as svc_class
    
    def add(self, args):
        if len(args) != 2:
            raise CLIUsageError('Must have two arguments')
        
        self.client.add_type(args[0], '<NULL>', args[1])
    
    add.cmd_name = 'add'
    add.cmd_usage = 'add type ref'
    add.cmd_info = 'Adds type (i.e. from stdint) to TypeDB'
    
    def list_types(self, args):
        feeder = self.client.list_types()
        
        fmt = '%12s %12s   %s' 
        print fmt % ('NAME', 'REF', 'COORD')
        
        for tl in feeder:
            name = tl[0]
            for t in tl[1]:
                ref = t[0]
                coord = t[1]
                
                print fmt % (name, ref, coord)
    
    list_types.cmd_name = 'list'
    list_types.cmd_usage = 'list'
    list_types.cmd_info = 'List types in TypeDB'
    
    def delete(self, args):
        if len(args) == 0:
            raise CLIUsageError('Must be at least one argument')
        
        self.client.delete_type(args[0])
    
    delete.cmd_name = 'delete'
    delete.cmd_usage = 'delete {type}'
    delete.cmd_info = 'Delete all references for {type}'

class CLIMacrosDB(CLISubsystem, CLIService):
    name = "macros"
    svc_name = "svc.macrosdb"
    
    from salsa.service.macrodb import MacrosDB as svc_class
    
    def list_macroses(self, args):
        feeder = self.client.list_macroses()
        
        fmt = '%32s %32s   %s' 
        if '-v' not in args:
            print fmt % ('NAME', 'ARGS', 'COORD')
        
        for m in feeder:
            mid, name, margs, body, src_name, src_line = m
            coordstr = src_name + '@' + str(src_line)                
            
            if '-v' in args:
                print 'ID:', mid
                print 'NAME:', name
                print 'ARGS:', '(' + margs + ')'
                print 'COORD:', coordstr                
                print body.strip()
                print
            else:
                print fmt % (name, margs, coordstr)
    
    list_macroses.cmd_name = 'list'
    list_macroses.cmd_usage = 'list [-v]'
    list_macroses.cmd_info = 'List macroses in MacrosDB. -v to show their bodies'

    def delete(self, args):
        if len(args) == 0:
            raise CLIUsageError('Must be at least one argument')
        
        for arg in args:
            self.client.delete_macros(arg)
    
    delete.cmd_name = 'delete'
    delete.cmd_usage = 'delete {macros} [macros..]'
    delete.cmd_info = 'Delete all macros references'

class CLI:
    system = None
    subsystems = {}
    usagestring = """salsacli [-c [corehost:]port] [-?] [subsys] command [options]"""
    
    def __init__(self):
        self.subsys = None
        self.core = None
    
    @classmethod
    def add_subsys(cls, subsys):
        subsys.add_commands()
        cls.subsystems[subsys.name] = subsys
    
    def create_core_client(self, args):       
        optlist, extargs =  getopt.getopt(args, 'c:?h')
        options = dict(optlist)
        
        if '-?' in options:
            self.print_usage()
            sys.exit(1)
            
        if '-c' in options:
            address = options['-c']
           
            if ':' in address:
                os.environ['SALSA_CORE_PORT'], os.environ['SALSA_HOST'] = address.split(':', 1)
            else:
                os.environ['SALSA_CORE_PORT'] = address        
        
        self.core = CoreClient()
        
        return extargs
    
    def print_usage(self):
        print >> sys.stderr, self.usagestring
        
        print >> sys.stderr, "Default subsystem - CORE. Where subsystems and commands are:\n"
        
        for (ss_name, subsys) in self.subsystems.items():
            print >> sys.stderr, ss_name.upper()
            
            subsys().print_usage() 
    
    def print_help(self):
        print >> sys.stderr, motd, '\n' 
        print >> sys.stderr, 'Usage: ', self.usagestring
        
        for (ss_name, subsys) in self.subsystems.items():
            print >> sys.stderr, ss_name.upper()
            
            for cmd_name in subsys.subcommands:
                subsys.print_cmd_usage(cmd_name)
    
    def exec_cmd(self, args):
        if len(args) == 1 and args[0] == '-?':
            self.print_help()
            sys.exit(0)
           
        args = self.create_core_client(args)
        
        if not args or len(args) == 0:   
            self.print_usage()
            sys.exit(0)
        
        ss_name = args.pop(0).lower()
        
        if ss_name not in self.subsystems:
            cmd_name = ss_name
            ss_name = 'core'
        else:
            cmd_name = args.pop(0).lower()
          
        self.subsys = self.subsystems[ss_name]()
        
        try:
            self.subsys.exec_cmd(cmd_name, args)
        except IOTransactionError as iote:
            print >> sys.stderr, str(iote)
            sys.exit(2)
    
    def stop(self):
        if self.core:
            self.core.stop()
        
        if self.subsys:
            self.subsys.stop()
    
    def run(self):
        try:
            self.exec_cmd(sys.argv[1:]) 
        except SALSACoreException, e:
            print >> sys.stderr, str(e)
            sys.exit(1)
        except KeyboardInterrupt, e:
            import traceback
            print >> sys.stderr, traceback.print_exc()
            
            print >> sys.stderr, "Interrupted"
            sys.exit(1)
        except SystemExit, e:
            raise
        except Exception, e:
            import traceback
            print >> sys.stderr, 'Internal error, please report to Sergey.Klyaus@Tune-IT.Ru'
            print >> sys.stderr, traceback.print_exc()
            sys.exit(1)

CLI.add_subsys(CLICore)
CLI.add_subsys(CLIRepository)
CLI.add_subsys(CLISourceTree)
CLI.add_subsys(CLITypeDB)
CLI.add_subsys(CLIMacrosDB)